/*
 * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */


package com.sun.jmx.snmp;


/**
 * The <CODE>BerEncoder</CODE> class is used for encoding data using BER.
 *
 * A <CODE>BerEncoder</CODE> needs to be set up with a byte buffer. The encoded
 * data are stored in this byte buffer.
 * <P>
 * NOTE : the buffer is filled from end to start. This means the caller
 * needs to encode its data in the reverse order.
 *
 *
 * <p><b>This API is a Sun Microsystems internal API  and is subject
 * to change without notice.</b></p>
 *
 * @since 1.5
 */

public class BerEncoder {

  /**
   * Constructs a new encoder and attaches it to the specified byte string.
   *
   * @param b The byte string containing the encoded data.
   */

  public BerEncoder(byte b[]) {
    bytes = b;
    start = b.length;
    stackTop = 0;
  }


  /**
   * Trim the encoding data and returns the length of the encoding.
   *
   * The encoder does backward encoding : so the bytes buffer is filled from end to start. The
   * encoded data must be shift before the buffer can be used. This is the purpose of the
   * <CODE>trim</CODE> method.
   *
   * After a call to the <CODE>trim</CODE> method, the encoder is reinitialized and
   * <CODE>putXXX</CODE> overwrite any existing encoded data.
   *
   * @return The length of the encoded data.
   */

  public int trim() {
    final int result = bytes.length - start;

    // for (int i = start ; i < bytes.length ; i++) {
    //  bytes[i-start] = bytes[i] ;
    // }
    if (result > 0) {
      java.lang.System.arraycopy(bytes, start, bytes, 0, result);
    }

    start = bytes.length;
    stackTop = 0;

    return result;
  }

  /**
   * Put an integer.
   *
   * @param v The integer to encode.
   */

  public void putInteger(int v) {
    putInteger(v, IntegerTag);
  }


  /**
   * Put an integer with the specified tag.
   *
   * @param v The integer to encode.
   * @param tag The tag to encode.
   */

  public void putInteger(int v, int tag) {
    putIntegerValue(v);
    putTag(tag);
  }


  /**
   * Put an integer expressed as a long.
   *
   * @param v The long to encode.
   */

  public void putInteger(long v) {
    putInteger(v, IntegerTag);
  }


  /**
   * Put an integer expressed as a long with the specified tag.
   *
   * @param v The long to encode
   * @param tag The tag to encode.
   */

  public void putInteger(long v, int tag) {
    putIntegerValue(v);
    putTag(tag);
  }


  /**
   * Put an octet string.
   *
   * @param s The bytes to encode
   */

  public void putOctetString(byte[] s) {
    putOctetString(s, OctetStringTag);
  }


  /**
   * Put an octet string with a specified tag.
   *
   * @param s The bytes to encode
   * @param tag The tag to encode.
   */

  public void putOctetString(byte[] s, int tag) {
    putStringValue(s);
    putTag(tag);
  }


  /**
   * Put an object identifier.
   *
   * @param s The oid to encode.
   */

  public void putOid(long[] s) {
    putOid(s, OidTag);
  }


  /**
   * Put an object identifier with a specified tag.
   *
   * @param s The integer to encode.
   * @param tag The tag to encode.
   */

  public void putOid(long[] s, int tag) {
    putOidValue(s);
    putTag(tag);
  }


  /**
   * Put a <CODE>NULL</CODE> value.
   */

  public void putNull() {
    putNull(NullTag);
  }


  /**
   * Put a <CODE>NULL</CODE> value with a specified tag.
   *
   * @param tag The tag to encode.
   */

  public void putNull(int tag) {
    putLength(0);
    putTag(tag);
  }


  /**
   * Put an <CODE>ANY</CODE> value. In fact, this method does not encode anything.
   * It simply copies the specified bytes into the encoding.
   *
   * @param s The encoding of the <CODE>ANY</CODE> value.
   */

  public void putAny(byte[] s) {
    putAny(s, s.length);
  }


  /**
   * Put an <CODE>ANY</CODE> value. Only the first <CODE>byteCount</CODE> are considered.
   *
   * @param s The encoding of the <CODE>ANY</CODE> value.
   * @param byteCount The number of bytes of the encoding.
   */

  public void putAny(byte[] s, int byteCount) {
    java.lang.System.arraycopy(s, 0, bytes, start - byteCount, byteCount);
    start -= byteCount;
    //    for (int i = byteCount - 1 ; i >= 0 ; i--) {
    //      bytes[--start] = s[i] ;
    //    }
  }


  /**
   * Open a sequence.
   * The encoder push the current position on its stack.
   */

  public void openSequence() {
    stackBuf[stackTop++] = start;
  }


  /**
   * Close a sequence.
   * The decode pull the stack to know the end of the current sequence.
   */

  public void closeSequence() {
    closeSequence(SequenceTag);
  }


  /**
   * Close a sequence with the specified tag.
   */

  public void closeSequence(int tag) {
    final int end = stackBuf[--stackTop];
    putLength(end - start);
    putTag(tag);
  }


  //
  // Some standard tags
  //
  public final static int BooleanTag = 1;
  public final static int IntegerTag = 2;
  public final static int OctetStringTag = 4;
  public final static int NullTag = 5;
  public final static int OidTag = 6;
  public final static int SequenceTag = 0x30;

  ////////////////////////// PROTECTED ///////////////////////////////


  /**
   * Put a tag and move the current position backward.
   *
   * @param tag The tag to encode.
   */

  protected final void putTag(int tag) {
    if (tag < 256) {
      bytes[--start] = (byte) tag;
    } else {
      while (tag != 0) {
        bytes[--start] = (byte) (tag & 127);
        tag = tag << 7;
      }
    }
  }


  /**
   * Put a length and move the current position backward.
   *
   * @param length The length to encode.
   */

  protected final void putLength(final int length) {
    if (length < 0) {
      throw new IllegalArgumentException();
    } else if (length < 128) {
      bytes[--start] = (byte) length;
    } else if (length < 256) {
      bytes[--start] = (byte) length;
      bytes[--start] = (byte) 0x81;
    } else if (length < 65536) {
      bytes[--start] = (byte) (length);
      bytes[--start] = (byte) (length >> 8);
      bytes[--start] = (byte) 0x82;
    } else if (length < 16777126) {
      bytes[--start] = (byte) (length);
      bytes[--start] = (byte) (length >> 8);
      bytes[--start] = (byte) (length >> 16);
      bytes[--start] = (byte) 0x83;
    } else {
      bytes[--start] = (byte) (length);
      bytes[--start] = (byte) (length >> 8);
      bytes[--start] = (byte) (length >> 16);
      bytes[--start] = (byte) (length >> 24);
      bytes[--start] = (byte) 0x84;
    }
  }


  /**
   * Put an integer value and move the current position backward.
   *
   * @param v The integer to encode.
   */

  protected final void putIntegerValue(int v) {
    final int end = start;
    int mask = 0x7f800000;
    int byteNeeded = 4;
    if (v < 0) {
      while (((mask & v) == mask) && (byteNeeded > 1)) {
        mask = mask >> 8;
        byteNeeded--;
      }
    } else {
      while (((mask & v) == 0) && (byteNeeded > 1)) {
        mask = mask >> 8;
        byteNeeded--;
      }
    }
    for (int i = 0; i < byteNeeded; i++) {
      bytes[--start] = (byte) v;
      v = v >> 8;
    }
    putLength(end - start);
  }


  /**
   * Put an integer value expressed as a long.
   *
   * @param v The integer to encode.
   */

  protected final void putIntegerValue(long v) {
    final int end = start;
    long mask = 0x7f80000000000000L;
    int byteNeeded = 8;
    if (v < 0) {
      while (((mask & v) == mask) && (byteNeeded > 1)) {
        mask = mask >> 8;
        byteNeeded--;
      }
    } else {
      while (((mask & v) == 0) && (byteNeeded > 1)) {
        mask = mask >> 8;
        byteNeeded--;
      }
    }
    for (int i = 0; i < byteNeeded; i++) {
      bytes[--start] = (byte) v;
      v = v >> 8;
    }
    putLength(end - start);
  }


  /**
   * Put a byte string and move the current position backward.
   *
   * @param s The byte string to encode.
   */

  protected final void putStringValue(byte[] s) {
    final int datalen = s.length;
    java.lang.System.arraycopy(s, 0, bytes, start - datalen, datalen);
    start -= datalen;
    // for (int i = s.length - 1 ; i >= 0 ; i--) {
    //   bytes[--start] = s[i] ;
    // }
    putLength(datalen);
  }


  /**
   * Put an oid and move the current position backward.
   *
   * @param s The oid to encode.
   */

  protected final void putOidValue(final long[] s) {
    final int end = start;
    final int slength = s.length;

    // bugId 4641746: 0, 1, and 2 are legal values.
    if ((slength < 2) || (s[0] > 2) || (s[1] >= 40)) {
      throw new IllegalArgumentException();
    }
    for (int i = slength - 1; i >= 2; i--) {
      long c = s[i];
      if (c < 0) {
        throw new IllegalArgumentException();
      } else if (c < 128) {
        bytes[--start] = (byte) c;
      } else {
        bytes[--start] = (byte) (c & 127);
        c = c >> 7;
        while (c != 0) {
          bytes[--start] = (byte) (c | 128);
          c = c >> 7;
        }
      }
    }
    bytes[--start] = (byte) (s[0] * 40 + s[1]);
    putLength(end - start);
  }


  //
  // This is the byte array containing the encoding.
  //
  protected final byte bytes[];

  //
  // This is the index of the first byte of the encoding.
  // It is initialized to <CODE>bytes.length</CODE> and decrease each time
  // an value is put in the encoder.
  //
  protected int start = -1;

  //
  // This is the stack where end of sequences are kept.
  // A value is computed and pushed in it each time the <CODE>openSequence</CODE> method
  // is invoked.
  // A value is pulled and checked each time the <CODE>closeSequence</CODE> method is called.
  //
  protected final int stackBuf[] = new int[200];
  protected int stackTop = 0;

}
